﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarConstruction
{
    [Serializable]
    public sealed class GrammarGoalDescriptorTable : ReadOnlyDictionary<string, GrammarGoalDescriptor>
    {
        internal GrammarGoalDescriptorTable()
            : base(new Dictionary<string, GrammarGoalDescriptor>())
        {
        }

        internal void Define(GoalDeclaration n, GrammarTerminalTable tokens, GrammarNonterminalTable nonterminals, GrammarProductionTable productions)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (tokens == null)
                throw new ArgumentNullException(nameof(tokens));
            if (nonterminals == null)
                throw new ArgumentNullException(nameof(nonterminals));
            if (productions == null)
                throw new ArgumentNullException(nameof(productions));
            foreach (GoalDeclarator item in n.Items)
            {
                GrammarGoalDescriptor desc;
                if (Dictionary.TryGetValue(item.Name.Name, out desc))
                {
                    string message = string.Format(SR.GoalDefined, item.Name.Name.AsSingleQuote());
                    NameErrorException e = new NameErrorException(message);
                    e.AddLocation(desc.Context);
                    e.AddLocation(item.Name.Context);
                    throw e;
                }
                desc = new GrammarGoalDescriptor();
                desc.Number = Dictionary.Count;
                desc.Name = item.Name.Name;
                desc.Context = item.Name.Context;
                Dictionary.Add(item.Name.Name, desc);
                desc.GoalSymbol = nonterminals.LookupNamed(item.Name);
                desc.StartSymbol = nonterminals.DefineStart(desc.GoalSymbol, item.Name.Context);
                SyntacticExpression pattern = SyntacticExpression.NewNameReference(item.Name);
                GrammarProduction prod = productions.Define(desc.StartSymbol, pattern, desc);
                prod.AddSymbol(desc.GoalSymbol);
                HashSet<GrammarTerminalSymbol> endMarkers = tokens.Resolve(item.EndMarkers);
                desc.SetEndMarkers(endMarkers);
                desc.AddDefaultEndMarkers(tokens.EndMarkers);
                desc.AcceptProduction = prod;
            }
        }

        internal void AssignDefaultEndMarkers(GrammarTerminalTable tokens)
        {
            if (tokens == null)
                throw new ArgumentNullException(nameof(tokens));
            foreach (GrammarGoalDescriptor desc in Dictionary.Values)
                desc.AddDefaultEndMarkers(tokens.EndMarkers);
        }

        public GrammarGoalDescriptor Lookup(Identifier name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            GrammarGoalDescriptor desc;
            if (!Dictionary.TryGetValue(name.Name, out desc))
            {
                string message = string.Format(SR.GoalNotDefined, name.Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(name.Context);
                throw e;
            }
            return desc;
        }

        public sealed override string ToString()
        {
            return string.Join("\n", Dictionary.Values);
        }
    }
}
